Trò chơi Tic-Tac-Toe, game đánh caro full source code
2 // <summary>The Photon Chat Api enables clients to connect to a chat server and communicate with other clients.</summary>
3 // <remarks>ChatClient is the main class of this api.</remarks>
4 // <copyright company="Exit Games GmbH">Photon Chat Api - Copyright (C) 2014 Exit Games GmbH</copyright>
5 // ----------------------------------------------------------------------------------------------------------------------
6
7 #if UNITY_3_5 || UNITY_4 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_5
8 #define UNITY
9 #endif
10
11 namespace ExitGames.Client.Photon.Chat
12 {
13 using System;
14 using System.Diagnostics;
15 using System.Collections.Generic;
16 using ExitGames.Client.Photon;
17
18 /// <summary>Central class of the Photon Chat API to connect, handle channels and messages.</summary>
19 /// <remarks>
20 /// This class must be instantiated with a IChatClientListener instance to get the callbacks.
21 /// Integrate it into your game loop by calling Service regularly.
22 /// Call Connect with an AppId that is setup as Photon Chat application. Note: Connect covers multiple
23 /// messages between this client and the servers. A short workflow will connect you to a chat server.
24 ///
25 /// Each ChatClient resembles a user in chat (set in Connect). Each user automatically subscribes a channel
26 /// for incoming private messages and can message any other user privately.
27 /// Before you publish messages in any non-private channel, that channel must be subscribed.
28 ///
29 /// PublicChannels is a list of subscribed channels, containing messages and senders.
30 /// PrivateChannels contains all incoming and sent private messages.
31 /// </remarks>
32 public class ChatClient : IPhotonPeerListener
33 {
34 public string NameServerAddress = "ns.exitgamescloud.com";
35 /// <summary>The address of the actual chat server assigned from NameServer. Public for read only.</summary>
36 public string FrontendAddress { get; private set; }
37 /// <summary>Region used to connect to. Currently all chat is done in EU. It can make sense to use only one region for the whole game.</summary>
38 private string chatRegion = "EU";
39
40 /// <summary>Settable only before you connect! Defaults to "EU".</summary>
41 public string ChatRegion
42 {
43 get { return chatRegion; }
44 set { chatRegion = value; }
45 }
46
47 /// <summary>Settable only before you connect!</summary>
48 public AuthenticationValues CustomAuthenticationValues { get; set; }
49
50 /// <summary>Current state of the ChatClient. Also use CanChat.</summary>
51 public ChatState State { get; private set; }
52 public ChatDisconnectCause DisconnectedCause { get; private set; }
53 public bool CanChat { get { return this.State == ChatState.ConnectedToFrontEnd && this.HasPeer; } }
54 private bool HasPeer { get { return this.chatPeer != null; } }
55
56 /// <summary>The version of your client. A new version also creates a new "virtual app" to separate players from older client versions.</summary>
57 public string AppVersion { get; private set; }
58
59 /// <summary>The AppID as assigned from the Photon Cloud. If you host yourself, this is the "regular" Photon Server Application Name (most likely: "LoadBalancing").</summary>
60 public string AppId { get; private set; }
61
62 /// <summary>The unique ID of a user/person. It's not a nickname and we assume users with the same userID are the same person.</summary>
63 public string UserId { get; private set; }
64
65 public readonly Dictionary<string, ChatChannel> PublicChannels;
66 public readonly Dictionary<string, ChatChannel> PrivateChannels;
67
68
69 private readonly IChatClientListener listener = null;
70 private ChatPeer chatPeer = null;
71
72 private bool didAuthenticate;
73 private int msDeltaForServiceCalls = 50;
74 private int msTimestampOfLastServiceCall;
75
76 private const string ChatApppName = "chat";
77 private static readonly Dictionary<ConnectionProtocol, int> ProtocolToNameServerPort = new Dictionary<ConnectionProtocol, int>() { { ConnectionProtocol.Udp, 5058 }, { ConnectionProtocol.Tcp, 4533 } }; //, { ConnectionProtocol.RHttp, 6063 } };
78
79
80 public ChatClient(IChatClientListener listener)
81 {
82 this.listener = listener;
83 this.State = ChatState.Uninitialized;
84
85 this.PublicChannels = new Dictionary<string, ChatChannel>();
86 this.PrivateChannels = new Dictionary<string, ChatChannel>();
87 }
88
89 public bool Connect(string appId, string appVersion, string userId, AuthenticationValues authValues)
90 {
91 return this.Connect(this.NameServerAddress, ConnectionProtocol.Udp, appId, appVersion, userId, authValues);
92 }
93
94 public bool Connect(string address, ConnectionProtocol protocol, string appId, string appVersion, string userId, AuthenticationValues authValues)
95 {
96 if (!this.HasPeer)
97 {
98 this.chatPeer = new ChatPeer(this, protocol);
99 }
100 else
101 {
102 this.Disconnect();
103 if (this.chatPeer.UsedProtocol != protocol)
104 {
105 this.chatPeer = new ChatPeer(this, protocol);
106 }
107 }
108
109 #if UNITY
110 #pragma warning disable 0162 // the library variant defines if we should use PUN's SocketUdp variant (at all)
111 if (PhotonPeer.NoSocket)
112 {
113 #if !UNITY_EDITOR && (UNITY_PS3 || UNITY_ANDROID)
114 UnityEngine.Debug.Log("Using class SocketUdpNativeDynamic");
115 this.chatPeer.SocketImplementation = typeof(SocketUdpNativeDynamic);
116 #elif !UNITY_EDITOR && UNITY_IPHONE
117 UnityEngine.Debug.Log("Using class SocketUdpNativeStatic");
118 this.chatPeer.SocketImplementation = typeof(SocketUdpNativeStatic);
119 #elif !UNITY_EDITOR && (UNITY_WINRT)
120 // this automatically uses a separate assembly-file with Win8-style Socket usage (not possible in Editor)
121 #else
122 Type udpSocket = Type.GetType("ExitGames.Client.Photon.SocketUdp, Assembly-CSharp");
123 this.chatPeer.SocketImplementation = udpSocket;
124 if (udpSocket == null)
125 {
126 UnityEngine.Debug.Log("ChatClient could not find a suitable C# socket class. The Photon3Unity3D.dll only supports native socket plugins.");
127 }
128 #endif
129 if (this.chatPeer.SocketImplementation == null)
130 {
131 UnityEngine.Debug.Log("No socket implementation set for 'NoSocket' assembly. Please contact Exit Games.");
132 }
133 }
134 #pragma warning restore 0162
135 #endif
136
137 this.chatPeer.TimePingInterval = 3000;
138 this.DisconnectedCause = ChatDisconnectCause.None;
139
140 this.CustomAuthenticationValues = authValues;
141 this.UserId = userId;
142 this.AppId = appId;
143 this.AppVersion = appVersion;
144 this.didAuthenticate = false;
145 this.msDeltaForServiceCalls = 100;
146
147
148 // clean all channels
149 this.PublicChannels.Clear();
150 this.PrivateChannels.Clear();
151
152 if (!address.Contains(":"))
153 {
154 int port = 0;
155 ProtocolToNameServerPort.TryGetValue(protocol, out port);
156 address = string.Format("{0}:{1}", address, port);
157 }
158
159 bool isConnecting = this.chatPeer.Connect(address, "NameServer");
160 if (isConnecting)
161 {
162 this.State = ChatState.ConnectingToNameServer;
163 }
164 return isConnecting;
165 }
166
167 /// <summary>
168 /// Must be called regularly to keep connection between client and server alive and to process incoming messages.
169 /// </summary>
170 /// <remarks>
171 /// This method limits the effort it does automatically using the private variable msDeltaForServiceCalls.
172 /// That value is lower for connect and multiplied by 4 when chat-server connection is ready.
173 /// </remarks>
174 public void Service()
175 {
176 if (this.HasPeer && (Environment.TickCount - msTimestampOfLastServiceCall > msDeltaForServiceCalls || msTimestampOfLastServiceCall == 0))
177 {
178 msTimestampOfLastServiceCall = Environment.TickCount;
179 this.chatPeer.Service(); //TODO: make sure to call service regularly. in best case it could be integrated into PhotonHandler.FallbackSendAckThread()!
180 }
181 }
182
183 public void Disconnect()
184 {
185 if (this.HasPeer && this.chatPeer.PeerState != PeerStateValue.Disconnected)
186 {
187 this.chatPeer.Disconnect();
188 }
189 }
190
191 public void StopThread()
192 {
193 if (this.HasPeer)
194 {
195 this.chatPeer.StopThread();
196 }
197 }
198
199 /// <summary>Sends operation to subscribe to a list of channels by name.</summary>
200 /// <param name="channels">List of channels to subscribe to. Avoid null or empty values.</param>
201 /// <returns>If the operation could be sent at all (Example: Fails if not connected to Chat Server).</returns>
202 public bool Subscribe(string[] channels)
203 {
204 return this.Subscribe(channels, 0);
205 }
206
207 /// <summary>
208 /// Sends operation to subscribe client to channels, optionally fetching a number of messages from the cache.
209 /// </summary>
210 /// <remarks>
211 /// Subscribes channels will forward new messages to this user. Use PublishMessage to do so.
212 /// The messages cache is limited but can be useful to get into ongoing conversations, if that's needed.
213 /// </remarks>
214 /// <param name="channels">List of channels to subscribe to. Avoid null or empty values.</param>
215 /// <param name="messagesFromHistory">0: no history. 1 and higher: number of messages in history. -1: all available history.</param>
216 /// <returns>If the operation could be sent at all (Example: Fails if not connected to Chat Server).</returns>
217 public bool Subscribe(string[] channels, int messagesFromHistory)
218 {
219 if (!this.CanChat)
220 {
221 // TODO: log error
222 return false;
223 }
224
225 if (channels == null || channels.Length == 0)
226 {
227 this.LogWarning("Subscribe can't be called for empty or null cannels-list.");
228 return false;
229 }
230
231 return this.SendChannelOperation(channels, (byte)ChatOperationCode.Subscribe, messagesFromHistory);
232 }
233
234 /// <summary>Unsubscribes from a list of channels, which stops getting messages from those.</summary>
235 /// <remarks>
236 /// The client will remove these channels from the PublicChannels dictionary once the server sent a response to this request.
237 ///
238 /// The request will be sent to the server and IChatClientListener.OnUnsubscribed gets called when the server
239 /// actually removed the channel subscriptions.
240 ///
241 /// Unsubscribe will fail if you include null or empty channel names.
242 /// </remarks>
243 /// <param name="channels">Names of channels to unsubscribe.</param>
244 /// <returns>False, if not connected to a chat server.</returns>
245 public bool Unsubscribe(string[] channels)
246 {
247 if (!this.CanChat)
248 {
249 // TODO: log error
250 return false;
251 }
252
253 if (channels == null || channels.Length == 0)
254 {
255 this.LogWarning("Unsubscribe can't be called for empty or null cannels-list.");
256 return false;
257 }
258
259 return SendChannelOperation(channels, ChatOperationCode.Unsubscribe, 0);
260 }
261
262 /// <summary>Sends a message to a public channel which this client subscribed to.</summary>
263 /// <remarks>
264 /// Before you publish to a channel, you have to subscribe it.
265 /// Everyone in that channel will get the message.
266 /// </remarks>
267 /// <param name="channelName">Name of the channel to publish to.</param>
268 /// <param name="message">Your message (string or any serializable data).</param>
269 /// <returns>False if the client is not yet ready to send messages.</returns>
270 public bool PublishMessage(string channelName, object message)
271 {
272 if (!this.CanChat)
273 {
274 // TODO: log error
275 return false;
276 }
277
278 if (string.IsNullOrEmpty(channelName) || message == null)
279 {
280 this.LogWarning("PublishMessage parameters must be non-null and not empty.");
281 return false;
282 }
283
284 Dictionary<byte, object> parameters = new Dictionary<byte, object>
285 {
286 { (byte)ChatParameterCode.Channel, channelName },
287 { (byte)ChatParameterCode.Message, message }
288 };
289
290 return this.chatPeer.OpCustom((byte)ChatOperationCode.Publish, parameters, true);
291 }
292
293 /// <summary>
294 /// Sends a private message to a single target user. Calls OnPrivateMessage on the receiving client.
295 /// </summary>
296 /// <param name="target">Username to send this message to.</param>
297 /// <param name="message">The message you want to send. Can be a simple string or anything serializable.</param>
298 /// <returns>True if this clients can send the message to the server.</returns>
299 public bool SendPrivateMessage(string target, object message)
300 {
301 return SendPrivateMessage(target, message, false);
302 }
303
304 /// <summary>
305 /// Sends a private message to a single target user. Calls OnPrivateMessage on the receiving client.
306 /// </summary>
307 /// <param name="target">Username to send this message to.</param>
308 /// <param name="message">The message you want to send. Can be a simple string or anything serializable.</param>
309 /// <param name="encrypt">Optionally, private messages can be encrypted. Encryption is not end-to-end as the server decrypts the message.</param>
310 /// <returns>True if this clients can send the message to the server.</returns>
311 public bool SendPrivateMessage(string target, object message, bool encrypt)
312 {
313 if (!this.CanChat)
314 {
315 // TODO: log error
316 return false;
317 }
318
319 if (string.IsNullOrEmpty(target) || message == null)
320 {
321 this.LogWarning("SendPrivateMessage parameters must be non-null and not empty.");
322 return false;
323 }
324
325 Dictionary<byte, object> parameters = new Dictionary<byte, object>
326 {
327 { ChatParameterCode.UserId, target },
328 { ChatParameterCode.Message, message }
329 };
330
331 bool sent = this.chatPeer.OpCustom((byte)ChatOperationCode.SendPrivate, parameters, true, 0, encrypt);
332 return sent;
333 }
334
335 /// <summary>Sets the user's status (pre-defined or custom) and an optional message.</summary>
336 /// <remarks>
337 /// The predefined status values can be found in class ChatUserStatus.
338 /// State ChatUserStatus.Invisible will make you offline for everyone and send no message.
339 ///
340 /// You can set custom values in the status integer. Aside from the pre-configured ones,
341 /// all states will be considered visible and online. Else, no one would see the custom state.
342 ///
343 /// The message object can be anything that Photon can serialize, including (but not limited to)
344 /// Hashtable, object[] and string. This value is defined by your own conventions.
345 /// </remarks>
346 /// <param name="status">Predefined states are in class ChatUserStatus. Other values can be used at will.</param>
347 /// <param name="message">Optional string message or null.</param>
348 /// <param name="skipMessage">If true, the message gets ignored. It can be null but won't replace any current message.</param>
349 /// <returns>True if the operation gets called on the server.</returns>
350 private bool SetOnlineStatus(int status, object message, bool skipMessage)
351 {
352 if (!this.CanChat)
353 {
354 // TODO: log error
355 return false;
356 }
357
358 Dictionary<byte, object> parameters = new Dictionary<byte, object>
359 {
360 { ChatParameterCode.Status, status },
361 };
362
363 if (skipMessage)
364 {
365 parameters[ChatParameterCode.SkipMessage] = true;
366 }
367 else
368 {
369 parameters[ChatParameterCode.Message] = message;
370 }
371 return this.chatPeer.OpCustom(ChatOperationCode.UpdateStatus, parameters, true);
372 }
373
374 /// <summary>Sets the user's status without changing your status-message.</summary>
375 /// <remarks>
376 /// The predefined status values can be found in class ChatUserStatus.
377 /// State ChatUserStatus.Invisible will make you offline for everyone and send no message.
378 ///
379 /// You can set custom values in the status integer. Aside from the pre-configured ones,
380 /// all states will be considered visible and online. Else, no one would see the custom state.
381 ///
382 /// This overload does not change the set message.
383 /// </remarks>
384 /// <param name="status">Predefined states are in class ChatUserStatus. Other values can be used at will.</param>
385 /// <returns>True if the operation gets called on the server.</returns>
386 public bool SetOnlineStatus(int status)
387 {
388 return SetOnlineStatus(status, null, true);
389 }
390 /// <summary>Sets the user's status without changing your status-message.</summary>
391 /// <remarks>
392 /// The predefined status values can be found in class ChatUserStatus.
393 /// State ChatUserStatus.Invisible will make you offline for everyone and send no message.
394 ///
395 /// You can set custom values in the status integer. Aside from the pre-configured ones,
396 /// all states will be considered visible and online. Else, no one would see the custom state.
397 ///
398 /// The message object can be anything that Photon can serialize, including (but not limited to)
399 /// Hashtable, object[] and string. This value is defined by your own conventions.
400 /// </remarks>
401 /// <param name="status">Predefined states are in class ChatUserStatus. Other values can be used at will.</param>
402 /// <param name="message">Also sets a status-message which your friends can get.</param>
403 /// <returns>True if the operation gets called on the server.</returns>
404 public bool SetOnlineStatus(int status, object message)
405 {
406 return SetOnlineStatus(status, message, false);
407 }
408
409 /// <summary>
410 /// Adds friends to a list on the Chat Server which will send you status updates for those.
411 /// </summary>
412 /// <remarks>
413 /// AddFriends and RemoveFriends enable clients to handle their friend list
414 /// in the Photon Chat server. Having users on your friends list gives you access
415 /// to their current online status (and whatever info your client sets in it).
416 ///
417 /// Each user can set an online status consisting of an integer and an arbitratry
418 /// (serializable) object. The object can be null, Hashtable, object[] or anything
419 /// else Photon can serialize.
420 ///
421 /// The status is published automatically to friends (anyone who set your user ID
422 /// with AddFriends).
423 ///
424 /// Photon flushes friends-list when a chat client disconnects, so it has to be
425 /// set each time. If your community API gives you access to online status already,
426 /// you could filter and set online friends in AddFriends.
427 ///
428 /// Actual friend relations are not persistent and have to be stored outside
429 /// of Photon.
430 /// </remarks>
431 /// <param name="friends">Array of friend userIds.</param>
432 /// <returns>If the operation could be sent.</returns>
433 public bool AddFriends(string[] friends)
434 {
435 if (!this.CanChat)
436 {
437 // TODO: log error
438 return false;
439 }
440
441 if (friends == null || friends.Length == 0)
442 {
443 this.LogWarning("AddFriends can't be called for empty or null list.");
444 return false;
445 }
446
447 Dictionary<byte, object> parameters = new Dictionary<byte, object>
448 {
449 { ChatParameterCode.Friends, friends },
450 };
451 return this.chatPeer.OpCustom(ChatOperationCode.AddFriends, parameters, true);
452 }
453
454 /// <summary>
455 /// Removes the provided entries from the list on the Chat Server and stops their status updates.
456 /// </summary>
457 /// <remarks>
458 /// Photon flushes friends-list when a chat client disconnects. Unless you want to
459 /// remove individual entries, you don't have to RemoveFriends.
460 ///
461 /// AddFriends and RemoveFriends enable clients to handle their friend list
462 /// in the Photon Chat server. Having users on your friends list gives you access
463 /// to their current online status (and whatever info your client sets in it).
464 ///
465 /// Each user can set an online status consisting of an integer and an arbitratry
466 /// (serializable) object. The object can be null, Hashtable, object[] or anything
467 /// else Photon can serialize.
468 ///
469 /// The status is published automatically to friends (anyone who set your user ID
470 /// with AddFriends).
471 ///
472 /// Photon flushes friends-list when a chat client disconnects, so it has to be
473 /// set each time. If your community API gives you access to online status already,
474 /// you could filter and set online friends in AddFriends.
475 ///
476 /// Actual friend relations are not persistent and have to be stored outside
477 /// of Photon.
478 ///
479 /// AddFriends and RemoveFriends enable clients to handle their friend list
480 /// in the Photon Chat server. Having users on your friends list gives you access
481 /// to their current online status (and whatever info your client sets in it).
482 ///
483 /// Each user can set an online status consisting of an integer and an arbitratry
484 /// (serializable) object. The object can be null, Hashtable, object[] or anything
485 /// else Photon can serialize.
486 ///
487 /// The status is published automatically to friends (anyone who set your user ID
488 /// with AddFriends).
489 ///
490 ///
491 /// Actual friend relations are not persistent and have to be stored outside
492 /// of Photon.
493 /// </remarks>
494 /// <param name="friends">Array of friend userIds.</param>
495 /// <returns>If the operation could be sent.</returns>
496 public bool RemoveFriends(string[] friends)
497 {
498 if (!this.CanChat)
499 {
500 // TODO: log error
501 return false;
502 }
503
504 if (friends == null || friends.Length == 0)
505 {
506 this.LogWarning("RemoveFriends can't be called for empty or null list.");
507 return false;
508 }
509
510 Dictionary<byte, object> parameters = new Dictionary<byte, object>
511 {
512 { ChatParameterCode.Friends, friends },
513 };
514 return this.chatPeer.OpCustom(ChatOperationCode.RemoveFriends, parameters, true);
515 }
516
517 /// <summary>
518 /// Get you the (locally used) channel name for the chat between this client and another user.
519 /// </summary>
520 /// <param name="userName">Remote user's name or UserId.</param>
521 /// <returns>The (locally used) channel name for a private channel.</returns>
522 public string GetPrivateChannelNameByUser(string userName)
523 {
524 return string.Format("{0}:{1}", this.UserId, userName);
525 }
526
527 /// <summary>
528 /// Simplified access to either private or public channels by name.
529 /// </summary>
530 /// <param name="channelName">Name of the channel to get. For private channels, the channel-name is composed of both user's names.</param>
531 /// <param name="isPrivate">Define if you expect a private or public channel.</param>
532 /// <param name="channel">Out parameter gives you the found channel, if any.</param>
533 /// <returns>True if the channel was found.</returns>
534 public bool TryGetChannel(string channelName, bool isPrivate, out ChatChannel channel)
535 {
536 if (!isPrivate)
537 {
538 return this.PublicChannels.TryGetValue(channelName, out channel);
539 }
540 else
541 {
542 return this.PrivateChannels.TryGetValue(channelName, out channel);
543 }
544 }
545
546 public void SendAcksOnly()
547 {
548 if (this.chatPeer != null) this.chatPeer.SendAcksOnly();
549 }
550
551
552 #region Private methods area
553
554 #region IPhotonPeerListener implementation
555
556 void IPhotonPeerListener.DebugReturn(DebugLevel level, string message)
557 {
558 #if UNITY_EDITOR || UNITY_STANDALONE
559 if (level == DebugLevel.ERROR)
560 {
561 UnityEngine.Debug.LogError(message);
562 }
563 else if (level == DebugLevel.WARNING)
564 {
565 UnityEngine.Debug.LogWarning(message);
566 }
567 else
568 {
569 UnityEngine.Debug.Log(message);
570 }
571 #else
572 Debug.WriteLine(message);
573 #endif
574 }
575
576 void IPhotonPeerListener.OnEvent(EventData eventData)
577 {
578 switch (eventData.Code)
579 {
580 case ChatEventCode.ChatMessages:
581 this.HandleChatMessagesEvent(eventData);
582 break;
583 case ChatEventCode.PrivateMessage:
584 this.HandlePrivateMessageEvent(eventData);
585 break;
586 case ChatEventCode.StatusUpdate:
587 this.HandleStatusUpdate(eventData);
588 break;
589 case ChatEventCode.Subscribe:
590 this.HandleSubscribeEvent(eventData);
591 break;
592 case ChatEventCode.Unsubscribe:
593 this.HandleUnsubscribeEvent(eventData);
594 break;
595 }
596 }
597
598 void IPhotonPeerListener.OnOperationResponse(OperationResponse operationResponse)
599 {
600 switch (operationResponse.OperationCode)
601 {
602 case (byte)ChatOperationCode.Authenticate:
603 this.HandleAuthResponse(operationResponse);
604 break;
605
606 // the following operations usually don't return useful data and no error.
607 case (byte)ChatOperationCode.Subscribe:
608 case (byte)ChatOperationCode.Unsubscribe:
609 case (byte)ChatOperationCode.Publish:
610 case (byte)ChatOperationCode.SendPrivate:
611 default:
612 if (operationResponse.ReturnCode != 0)
613 {
614 ((IPhotonPeerListener)this).DebugReturn(DebugLevel.ERROR, string.Format("Chat Operation {0} failed (Code: {1}). Debug Message: {2}", operationResponse.OperationCode, operationResponse.ReturnCode, operationResponse.DebugMessage));
615 }
616 break;
617 }
618 }
619
620 void IPhotonPeerListener.OnStatusChanged(StatusCode statusCode)
621 {
622 switch (statusCode)
623 {
624 case StatusCode.Connect:
625 this.chatPeer.EstablishEncryption();
626 if (this.State == ChatState.ConnectingToNameServer)
627 {
628 this.State = ChatState.ConnectedToNameServer;
629 this.listener.OnChatStateChange(this.State);
630 }
631 else if (this.State == ChatState.ConnectingToFrontEnd)
632 {
633 this.AuthenticateOnFrontEnd();
634 }
635 break;
636 case StatusCode.EncryptionEstablished:
637 // once encryption is availble, the client should send one (secure) authenticate. it includes the AppId (which identifies your app on the Photon Cloud)
638 if (!this.didAuthenticate)
639 {
640 this.didAuthenticate = this.chatPeer.AuthenticateOnNameServer(this.AppId, this.AppVersion, this.chatRegion, this.UserId, this.CustomAuthenticationValues);
641 if (!this.didAuthenticate)
642 {
643 ((IPhotonPeerListener) this).DebugReturn(DebugLevel.ERROR, "Error calling OpAuthenticate! Did not work. Check log output, CustomAuthenticationValues and if you're connected. State: " + this.State);
644 }
645 }
646 break;
647 case StatusCode.EncryptionFailedToEstablish:
648 this.State = ChatState.Disconnecting;
649 this.chatPeer.Disconnect();
650 break;
651 case StatusCode.Disconnect:
652 if (this.State == ChatState.Authenticated)
653 {
654 this.ConnectToFrontEnd();
655 }
656 else
657 {
658 this.State = ChatState.Disconnected;
659 this.listener.OnChatStateChange(ChatState.Disconnected);
660 this.listener.OnDisconnected();
661 }
662 break;
663 }
664 }
665
666 #if SDK_V4
667 void IPhotonPeerListener.OnMessage(object msg)
668 {
669 // in v4 interface IPhotonPeerListener
670 return;
671 }
672 #endif
673
674 #endregion
675
676 private bool SendChannelOperation(string[] channels, byte operation, int historyLength)
677 {
678 Dictionary<byte, object> opParameters = new Dictionary<byte, object> { { (byte)ChatParameterCode.Channels, channels } };
679
680 if (historyLength != 0)
681 {
682 opParameters.Add((byte)ChatParameterCode.HistoryLength, historyLength);
683 }
684
685 return this.chatPeer.OpCustom(operation, opParameters, true);
686 }
687
688 private void HandlePrivateMessageEvent(EventData eventData)
689 {
690 //Console.WriteLine(SupportClass.DictionaryToString(eventData.Parameters));
691
692 var message = (object)eventData.Parameters[(byte)ChatParameterCode.Message];
693 var sender = (string)eventData.Parameters[(byte)ChatParameterCode.Sender];
694
695 string channelName;
696 if (this.UserId != null && this.UserId.Equals(sender))
697 {
698 var target = (string)eventData.Parameters[(byte)ChatParameterCode.UserId];
699 channelName = this.GetPrivateChannelNameByUser(target);
700 }
701 else
702 {
703 channelName = this.GetPrivateChannelNameByUser(sender);
704 }
705
706 ChatChannel channel;
707 if (!this.PrivateChannels.TryGetValue(channelName, out channel))
708 {
709 channel = new ChatChannel(channelName);
710 channel.IsPrivate = true;
711 this.PrivateChannels.Add(channel.Name, channel);
712 }
713
714 channel.Add(sender, message);
715 this.listener.OnPrivateMessage(sender, message, channelName);
716 }
717
718 private void HandleChatMessagesEvent(EventData eventData)
719 {
720 var messages = (object[])eventData.Parameters[(byte)ChatParameterCode.Messages];
721 var senders = (string[])eventData.Parameters[(byte)ChatParameterCode.Senders];
722 var channelName = (string)eventData.Parameters[(byte)ChatParameterCode.Channel];
723
724 ChatChannel channel;
725 if (!this.PublicChannels.TryGetValue(channelName, out channel))
726 {
727 // TODO: log that channel wasn't found
728 return;
729 }
730
731 channel.Add(senders, messages);
732 this.listener.OnGetMessages(channelName, senders, messages);
733 }
734
735 private void HandleSubscribeEvent(EventData eventData)
736 {
737 var channelsInResponse = (string[])eventData.Parameters[ChatParameterCode.Channels];
738 var results = (bool[])eventData.Parameters[ChatParameterCode.SubscribeResults];
739
740 for (int i = 0; i < channelsInResponse.Length; i++)
741 {
742 if (results[i])
743 {
744 string channelName = channelsInResponse[i];
745 if (!this.PublicChannels.ContainsKey(channelName))
746 {
747 ChatChannel channel = new ChatChannel(channelName);
748 this.PublicChannels.Add(channel.Name, channel);
749 }
750 }
751 }
752
753 this.listener.OnSubscribed(channelsInResponse, results);
754 }
755
756 private void HandleUnsubscribeEvent(EventData eventData)
757 {
758 var channelsInRequest = (string[])eventData[ChatParameterCode.Channels];
759 for (var i = 0; i < channelsInRequest.Length; i++)
760 {
761 string channelName = channelsInRequest[i];
762 this.PublicChannels.Remove(channelName);
763 }
764
765 this.listener.OnUnsubscribed(channelsInRequest);
766 }
767
768 private void HandleAuthResponse(OperationResponse operationResponse)
769 {
770 ((IPhotonPeerListener)this).DebugReturn(DebugLevel.INFO, operationResponse.ToStringFull() + " on: " + this.NameServerAddress);
771 if (operationResponse.ReturnCode == 0)
772 {
773 if (this.State == ChatState.ConnectedToNameServer)
774 {
775 this.State = ChatState.Authenticated;
776 this.listener.OnChatStateChange(this.State);
777
778 if (operationResponse.Parameters.ContainsKey(ParameterCode.Secret))
779 {
780 if (this.CustomAuthenticationValues == null)
781 {
782 this.CustomAuthenticationValues = new AuthenticationValues();
783 }
784 this.CustomAuthenticationValues.Secret = operationResponse[ParameterCode.Secret] as string;
785 this.FrontendAddress = (string) operationResponse[ParameterCode.Address];
786
787 // we disconnect and status handler starts to connect to front end
788 this.chatPeer.Disconnect();
789 }
790 else
791 {
792 //TODO: error reaction!
793 }
794 }
795 else if (this.State == ChatState.ConnectingToFrontEnd)
796 {
797 this.msDeltaForServiceCalls = this.msDeltaForServiceCalls * 4; // when we arrived on chat server: limit Service calls some more
798
799 this.State = ChatState.ConnectedToFrontEnd;
800 this.listener.OnChatStateChange(this.State);
801 this.listener.OnConnected();
802 }
803 }
804 else
805 {
806 //((IPhotonPeerListener)this).DebugReturn(DebugLevel.INFO, operationResponse.ToStringFull() + " NS: " + this.NameServerAddress + " FrontEnd: " + this.frontEndAddress);
807
808 switch (operationResponse.ReturnCode)
809 {
810 case ErrorCode.InvalidAuthentication:
811 this.DisconnectedCause = ChatDisconnectCause.InvalidAuthentication;
812 break;
813 case ErrorCode.CustomAuthenticationFailed:
814 this.DisconnectedCause = ChatDisconnectCause.CustomAuthenticationFailed;
815 break;
816 case ErrorCode.InvalidRegion:
817 this.DisconnectedCause = ChatDisconnectCause.InvalidRegion;
818 break;
819 case ErrorCode.MaxCcuReached:
820 this.DisconnectedCause = ChatDisconnectCause.MaxCcuReached;
821 break;
822 case ErrorCode.OperationNotAllowedInCurrentState:
823 this.DisconnectedCause = ChatDisconnectCause.OperationNotAllowedInCurrentState;
824 break;
825 }
826
827 this.State = ChatState.Disconnecting;
828 this.chatPeer.Disconnect();
829 }
830 }
831
832 private void HandleStatusUpdate(EventData eventData)
833 {
834 var user = (string)eventData.Parameters[ChatParameterCode.Sender];
835 var status = (int)eventData.Parameters[ChatParameterCode.Status];
836
837 object message = null;
838 bool gotMessage = eventData.Parameters.ContainsKey(ChatParameterCode.Message);
839 if (gotMessage)
840 {
841 message = eventData.Parameters[ChatParameterCode.Message];
842 }
843
844 this.listener.OnStatusUpdate(user, status, gotMessage, message);
845 }
846
847 private void ConnectToFrontEnd()
848 {
849 this.State = ChatState.ConnectingToFrontEnd;
850
851 this.chatPeer.Connect(this.FrontendAddress, ChatApppName);
852 }
853
854 private bool AuthenticateOnFrontEnd()
855 {
856 if (CustomAuthenticationValues != null)
857 {
858 var d = new Dictionary<byte, object> {{(byte)ChatParameterCode.Secret, CustomAuthenticationValues.Secret}};
859 return this.chatPeer.OpCustom((byte)ChatOperationCode.Authenticate, d, true);
860 }
861 else
862 {
863 Debug.WriteLine("Can't authenticate on front end server. CustomAuthValues is null");
864 }
865 return false;
866 }
867
868 private void LogWarning(string message)
869 {
870 #if UNITY
871 UnityEngine.Debug.LogWarning(message);
872 #else
873 Debug.WriteLine(message, "Warning");
874 #endif
875 }
876
877 private void Log(string message)
878 {
879 #if UNITY
880 UnityEngine.Debug.Log(message);
881 #else
882 Debug.WriteLine(message);
883 #endif
884 }
885
886 #endregion
887 }
888 }
----------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------
This class must be instantiated with a IChatClientListener instance to get the callbacks.
Integrate it into your game loop by calling Service regularly.
Call Connect with an AppId that is setup as Photon Chat application. Note: Connect covers multiple
messages between this client and the servers. A short workflow will connect you to a chat server.
Each ChatClient resembles a user in chat (set in Connect). Each user automatically subscribes a channel
for incoming private messages and can message any other user privately.
Before you publish messages in any non-private channel, that channel must be subscribed.
PublicChannels is a list of subscribed channels, containing messages and senders.
PrivateChannels contains all incoming and sent private messages.
private static readonly Dictionary
#pragma warning disable 0162 the library variant defines if we should use PUN's SocketUdp variant (at all)
this automatically uses a separate assembly-file with Win8-style Socket usage (not possible in Editor)
clean all channels
Must be called regularly to keep connection between client and server alive and to process incoming messages.
This method limits the effort it does automatically using the private variable msDeltaForServiceCalls.
That value is lower for connect and multiplied by 4 when chat-server connection is ready.
this.chatPeer.Service(); TODO: make sure to call service regularly. in best case it could be integrated into PhotonHandler.FallbackSendAckThread()!
List of channels to subscribe to. Avoid null or empty values.
Sends operation to subscribe client to channels, optionally fetching a number of messages from the cache.
Subscribes channels will forward new messages to this user. Use PublishMessage to do so.
The messages cache is limited but can be useful to get into ongoing conversations, if that's needed.
List of channels to subscribe to. Avoid null or empty values.
0: no history. 1 and higher: number of messages in history. -1: all available history.
TODO: log error
The client will remove these channels from the PublicChannels dictionary once the server sent a response to this request.
The request will be sent to the server and IChatClientListener.OnUnsubscribed gets called when the server
actually removed the channel subscriptions.
Unsubscribe will fail if you include null or empty channel names.
Names of channels to unsubscribe.
TODO: log error
Before you publish to a channel, you have to subscribe it.
Everyone in that channel will get the message.
Name of the channel to publish to.
Your message (string or any serializable data).
TODO: log error
Sends a private message to a single target user. Calls OnPrivateMessage on the receiving client.
Username to send this message to.
The message you want to send. Can be a simple string or anything serializable.
Sends a private message to a single target user. Calls OnPrivateMessage on the receiving client.
Username to send this message to.
The message you want to send. Can be a simple string or anything serializable.
Optionally, private messages can be encrypted. Encryption is not end-to-end as the server decrypts the message.
TODO: log error
The predefined status values can be found in class ChatUserStatus.
State ChatUserStatus.Invisible will make you offline for everyone and send no message.
You can set custom values in the status integer. Aside from the pre-configured ones,
all states will be considered visible and online. Else, no one would see the custom state.
The message object can be anything that Photon can serialize, including (but not limited to)
Hashtable, object[] and string. This value is defined by your own conventions.
Predefined states are in class ChatUserStatus. Other values can be used at will.
Optional string message or null.
If true, the message gets ignored. It can be null but won't replace any current message.
TODO: log error
The predefined status values can be found in class ChatUserStatus.
State ChatUserStatus.Invisible will make you offline for everyone and send no message.
You can set custom values in the status integer. Aside from the pre-configured ones,
all states will be considered visible and online. Else, no one would see the custom state.
This overload does not change the set message.
Predefined states are in class ChatUserStatus. Other values can be used at will.
The predefined status values can be found in class ChatUserStatus.
State ChatUserStatus.Invisible will make you offline for everyone and send no message.
You can set custom values in the status integer. Aside from the pre-configured ones,
all states will be considered visible and online. Else, no one would see the custom state.
The message object can be anything that Photon can serialize, including (but not limited to)
Hashtable, object[] and string. This value is defined by your own conventions.
Predefined states are in class ChatUserStatus. Other values can be used at will.
Also sets a status-message which your friends can get.
Adds friends to a list on the Chat Server which will send you status updates for those.
AddFriends and RemoveFriends enable clients to handle their friend list
in the Photon Chat server. Having users on your friends list gives you access
to their current online status (and whatever info your client sets in it).
Each user can set an online status consisting of an integer and an arbitratry
(serializable) object. The object can be null, Hashtable, object[] or anything
else Photon can serialize.
The status is published automatically to friends (anyone who set your user ID
with AddFriends).
Photon flushes friends-list when a chat client disconnects, so it has to be
set each time. If your community API gives you access to online status already,
you could filter and set online friends in AddFriends.
Actual friend relations are not persistent and have to be stored outside
of Photon.
Array of friend userIds.
TODO: log error
Removes the provided entries from the list on the Chat Server and stops their status updates.
Photon flushes friends-list when a chat client disconnects. Unless you want to
remove individual entries, you don't have to RemoveFriends.
AddFriends and RemoveFriends enable clients to handle their friend list
in the Photon Chat server. Having users on your friends list gives you access
to their current online status (and whatever info your client sets in it).
Each user can set an online status consisting of an integer and an arbitratry
(serializable) object. The object can be null, Hashtable, object[] or anything
else Photon can serialize.
The status is published automatically to friends (anyone who set your user ID
with AddFriends).
Photon flushes friends-list when a chat client disconnects, so it has to be
set each time. If your community API gives you access to online status already,
you could filter and set online friends in AddFriends.
Actual friend relations are not persistent and have to be stored outside
of Photon.
AddFriends and RemoveFriends enable clients to handle their friend list
in the Photon Chat server. Having users on your friends list gives you access
to their current online status (and whatever info your client sets in it).
Each user can set an online status consisting of an integer and an arbitratry
(serializable) object. The object can be null, Hashtable, object[] or anything
else Photon can serialize.
The status is published automatically to friends (anyone who set your user ID
with AddFriends).
Actual friend relations are not persistent and have to be stored outside
of Photon.
Array of friend userIds.
TODO: log error
Get you the (locally used) channel name for the chat between this client and another user.
Remote user's name or UserId.
Simplified access to either private or public channels by name.
Name of the channel to get. For private channels, the channel-name is composed of both user's names.
Define if you expect a private or public channel.
Out parameter gives you the found channel, if any.
the following operations usually don't return useful data and no error.
once encryption is availble, the client should send one (secure) authenticate. it includes the AppId (which identifies your app on the Photon Cloud)
in v4 interface IPhotonPeerListener
Console.WriteLine(SupportClass.DictionaryToString(eventData.Parameters));
TODO: log that channel wasn't found
we disconnect and status handler starts to connect to front end
TODO: error reaction!
this.msDeltaForServiceCalls = this.msDeltaForServiceCalls * 4; when we arrived on chat server: limit Service calls some more
((IPhotonPeerListener)this).DebugReturn(DebugLevel.INFO, operationResponse.ToStringFull() + " NS: " + this.NameServerAddress + " FrontEnd: " + this.frontEndAddress);